package ui;

/**
 * @author peedarpk
 */
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BoxLayout;
import javax.swing.JPanel;

import lights.Phase;

import obj.Edge;
import obj.Junction;
import obj.Node;

public class MapEditor extends JPanel implements MouseListener, MouseMotionListener {

	private static final long serialVersionUID = 7218471721638221815L;
	private int panelWidth = 120;
	private int panelHeight = 60;
	private int nodeCount = 0;
	private int edgeCount = 0;
	private List<Node> nodesList = new ArrayList<Node>();
	private List<Edge> edgesList = new ArrayList<Edge>();
	private List<Junction> junctionList = new ArrayList<Junction>();
	private Node[][] nodesMatrix = new Node[120][60];
	private boolean rightClick = false;
	private Point startDrag, endDrag;

	PopUpMenu popupMenu = new PopUpMenu();
	/** The rendered size of the tile (in pixels) */
	public static final int TILE_SIZE = 10;

	public MapEditor(Dimension d) {
		setBackground(Color.WHITE);

		setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
		this.add(popupMenu);
		addMouseListener(this);
		addMouseMotionListener(this);
		panelWidth = d.width / TILE_SIZE;
		panelHeight = (d.height - 200) / TILE_SIZE;
		nodesMatrix = new Node[panelWidth][panelHeight];
	}

	public void setData(List<Edge> elist, List<Node> nList, List<Junction> jList) {
		this.edgesList = elist;
		this.nodesList = nList;
		this.junctionList = jList;
	}

	private Edge makeEdge(Point start, Point end) {

		Node startNode = nodesMatrix[start.x / TILE_SIZE][start.y / TILE_SIZE];

		Node endNode = nodesMatrix[end.x / TILE_SIZE][end.y / TILE_SIZE];

		if (!startNode.equals(endNode)) {
			Edge e = new Edge("e" + edgeCount, startNode, endNode);
			return e;
		}

		return null;
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		if (rightClick == false) {

			Node n = new Node(e.getX(), e.getY(), "Node" + nodeCount);
			if (nodesMatrix[n.getxBox()][n.getyBox()] == null) {
				nodeCount++;
				nodesList.add(n);
				nodesMatrix[n.getxBox()][n.getyBox()] = n;
				this.repaint();
			}
		}
		rightClick = false;
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		endDrag = new Point(e.getX(), e.getY());
		this.repaint();

	}

	@Override
	public void mouseEntered(MouseEvent e) {

	}

	@Override
	public void mouseExited(MouseEvent e) {

	}

	@Override
	public void mouseMoved(MouseEvent e) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
		int xBoxNo = e.getX() / TILE_SIZE;
		int yBoxNo = e.getY() / TILE_SIZE;
		if (e.isPopupTrigger()) {
			rightClick = true;
			if (nodesMatrix[xBoxNo][yBoxNo] != null) {
				popupMenu.setNodeAndPanel(this, nodesList, nodesMatrix[xBoxNo][yBoxNo], nodesMatrix, xBoxNo, yBoxNo,
						edgesList);
				popupMenu.show(this, e.getX(), e.getY());
			}

		}
		if (rightClick == false) {

			if (nodesMatrix[xBoxNo][yBoxNo] != null) {
				startDrag = new Point(e.getX(), e.getY());
				endDrag = startDrag;
				this.repaint();
			}
		}

	}

	@Override
	public void mouseReleased(MouseEvent e) {
		int xBoxNo = e.getX() / TILE_SIZE;
		int yBoxNo = e.getY() / TILE_SIZE;
		if (e.isPopupTrigger()) {
			rightClick = true;
			if (nodesMatrix[xBoxNo][yBoxNo] != null) {
				popupMenu.setNodeAndPanel(this, nodesList, nodesMatrix[xBoxNo][yBoxNo], nodesMatrix, xBoxNo, yBoxNo,
						edgesList);
				popupMenu.show(this, e.getX(), e.getY());
			}
		}
		if (rightClick == false) {

			if (nodesMatrix[xBoxNo][yBoxNo] != null) {
				Edge edge = makeEdge(startDrag, endDrag);

				if (edge != null) {
					edgesList.add(edge);
					edgeCount++;
					startDrag = null;
					endDrag = null;
					this.repaint();
				}

			}
		}

	}

	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;
		for (int x = 0; x < panelWidth; x++) {
			for (int y = 0; y < panelHeight; y++) {

				g.setColor(Color.GRAY.brighter());
				g.drawRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
			}
		}

		// Draw Junction
		for (Junction j : junctionList) {
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2d.setColor(new Color(246, 233, 169));
			g2d.setStroke(new BasicStroke(1));
			g2d.drawRect((int) j.getPosition().getX(), (int) j.getPosition().getY(), JUNCTION_WIDTH, JUNCTION_HEIGHT);
			g2d.fillRect((int) j.getPosition().getX(), (int) j.getPosition().getY(), JUNCTION_WIDTH, JUNCTION_HEIGHT);
			g2d.setColor(Color.gray);
			g2d.setStroke(new BasicStroke(1));
			g2d.fillRect((int) j.getPosition().getX(), (int) j.getPosition().getY(), TILE_SIZE, TILE_SIZE);
		}

		// Draw line on mouse drag
		if (startDrag != null && endDrag != null) {
			g2d.setPaint(Color.lightGray);
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2d.setStroke(new BasicStroke(10));
			g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.80f));
			g2d.drawLine(startDrag.x, startDrag.y, endDrag.x, endDrag.y);
		}
		// Draw edges
		for (Edge e : this.edgesList) {
			g2d.setPaint(Color.LIGHT_GRAY);
			g2d.setStroke(new BasicStroke(8));
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2d.drawLine(e.getStartNode().getxBox() * TILE_SIZE, e.getStartNode().getyBox() * TILE_SIZE, e.getEndNode()
					.getxBox() * TILE_SIZE, e.getEndNode().getyBox() * TILE_SIZE);

			// g2d.setPaint(Color.LIGHT_GRAY);
			// g2d.setStroke(new BasicStroke(16));
			// g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			// RenderingHints.VALUE_ANTIALIAS_ON);
			// g2d.drawLine(e.getStartNode().getxBox() * TILE_SIZE,
			// e.getStartNode().getyBox() * TILE_SIZE, e.getEndNode()
			// .getxBox() * TILE_SIZE, e.getEndNode().getyBox() * TILE_SIZE);

			Point tip = new Point(e.getStartNode().getxBox() * TILE_SIZE, e.getStartNode().getyBox() * TILE_SIZE);
			Point tail = new Point(e.getEndNode().getxBox() * TILE_SIZE, e.getEndNode().getyBox() * TILE_SIZE);
			Point middle = getCenterPoint(tip, tail);
			g2d.setStroke(new BasicStroke(2));
			drawArrowHead(g2d, middle, tip, Color.DARK_GRAY);
			g2d.setPaint(Color.BLUE);
			g2d.drawString(e.getName(), middle.x + 5, middle.y + 5);
		}

		// draw Nodes
		for (Node n : this.nodesList) {
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2d.setColor(Color.BLACK);
			g2d.setStroke(new BasicStroke(1));
			g2d.drawOval(n.getxBox() * TILE_SIZE, n.getyBox() * TILE_SIZE, TILE_SIZE, TILE_SIZE);
			g2d.fillOval(n.getxBox() * TILE_SIZE, n.getyBox() * TILE_SIZE, TILE_SIZE, TILE_SIZE);
			// g2d.setPaint(Color.BLUE);
			// g2d.drawString(n.getName(), n.getxBox() * TILE_SIZE, n.getyBox()
			// * TILE_SIZE);
		}

	}

	private static int JUNCTION_X_POSITION = 500;
	private static int JUNCTION_Y_POSITION = 200;
	private static int JUNCTION_WIDTH = 100;
	private static int JUNCTION_HEIGHT = 80;

	public void drawTJunction() {

		Junction junction = new Junction();
		junction.setPosition(new Point(JUNCTION_X_POSITION, JUNCTION_Y_POSITION));
		junction.setXboxPos(JUNCTION_X_POSITION / TILE_SIZE);
		junction.setYboxPos(JUNCTION_Y_POSITION / TILE_SIZE);

		// add nodes to the junction - on the left
		int gap = 0;
		for (int i = 0; i < 4; i++) {

			if (i == 0) {
				gap = 10;
			} else {
				gap = gap + 20;
			}
			Node n = new Node(JUNCTION_X_POSITION - TILE_SIZE, JUNCTION_Y_POSITION + gap, "Node" + i);
			if (nodesMatrix[n.getxBox()][n.getyBox()] == null) {
				nodeCount++;
				this.nodesList.add(n);
				this.nodesMatrix[n.getxBox()][n.getyBox()] = n;
			}
		}

		// add nodes to the junction - on the right
		gap = 0;
		for (int i = 4; i < 8; i++) {

			if (i == 4) {
				gap = 10;
			} else {
				gap = gap + 20;
			}
			Node n = new Node(JUNCTION_X_POSITION + (JUNCTION_WIDTH), JUNCTION_Y_POSITION + gap, "Node" + i);
			if (nodesMatrix[n.getxBox()][n.getyBox()] == null) {
				nodeCount++;
				this.nodesList.add(n);
				this.nodesMatrix[n.getxBox()][n.getyBox()] = n;
			}
		}

		/* add nodes to the junction - on the bottom */
		gap = 0;
		for (int i = 8; i < 12; i++) {

			if (i == 8) {
				gap = 10;
			} else {
				gap = gap + 20;
			}
			Node n = new Node(JUNCTION_X_POSITION + gap, JUNCTION_Y_POSITION + JUNCTION_HEIGHT, "Node" + i);

			if (nodesMatrix[n.getxBox()][n.getyBox()] == null) {
				nodeCount++;
				this.nodesList.add(n);
				this.nodesMatrix[n.getxBox()][n.getyBox()] = n;
			}
		}

		junction.setPhaseList(addPhasesForTJunction());
		junctionList.add(junction);

	}

	private List<Phase> addPhasesForTJunction() {

		List<Phase> phaseList = new ArrayList<Phase>();

		Node n0 = getNodeByName("Node0");
		Node n4 = getNodeByName("Node4");
		Node n3 = getNodeByName("Node3");
		Node n8 = getNodeByName("Node8");
		Node n7 = getNodeByName("Node7");
		Node n11 = getNodeByName("Node11");
		n0.setLight(true);
		n4.setLight(true);
		n3.setLight(true);
		n8.setLight(true);
		n7.setLight(true);
		n11.setLight(true);

		n0.setVehicleIn(true);
		n8.setVehicleIn(true);
		n7.setVehicleIn(true);
		n4.setVehicleIn(false);
		n11.setVehicleIn(false);
		n3.setVehicleIn(false);

		// Add phase 1
		Phase phase1 = new Phase();
		phase1.setName("phase1");
		phase1.setGreenTime(15000);
		List<Node> phase1List = new ArrayList<Node>();
		phase1List.add(n0);
		phase1List.add(n4);
		phase1List.add(n3);
		phase1List.add(n8);
		phase1List.add(n7);
		phase1List.add(n11);

		Node n1 = getNodeByName("Node1");
		Node n10 = getNodeByName("Node10");
		n1.setLight(true);
		n1.setVehicleIn(true);
		n10.setLight(true);
		n10.setVehicleIn(false);
		phase1List.add(n1);
		phase1List.add(n10);
		phase1.setPhaseNodes(phase1List);
		phaseList.add(phase1);

		// Add phase 2
		Phase phase2 = new Phase();
		phase2.setName("phase2");
		phase2.setGreenTime(15000);
		List<Node> phase2List = new ArrayList<Node>();
		phase2List.add(n0);
		phase2List.add(n4);
		phase2List.add(n3);
		phase2List.add(n8);
		phase2List.add(n7);
		phase2List.add(n11);

		Node n6 = getNodeByName("Node6");
		Node n2 = getNodeByName("Node2");
		n6.setLight(true);
		n6.setVehicleIn(true);
		n2.setLight(true);
		n2.setVehicleIn(false);
		phase2List.add(n6);
		phase2List.add(n2);

		phase2.setPhaseNodes(phase2List);
		phaseList.add(phase2);

		// Add phase 3
		Phase phase3 = new Phase();
		phase3.setName("phase3");
		phase3.setGreenTime(15000);
		List<Node> phase3List = new ArrayList<Node>();
		phase3List.add(n0);
		phase3List.add(n4);
		phase3List.add(n3);
		phase3List.add(n8);
		phase3List.add(n7);
		phase3List.add(n11);

		Node n9 = getNodeByName("Node9");
		Node n5 = getNodeByName("Node5");
		n9.setLight(true);
		n5.setLight(true);
		n5.setVehicleIn(false);
		n9.setVehicleIn(true);
		phase3List.add(n9);
		phase3List.add(n5);

		phase3.setPhaseNodes(phase3List);
		phaseList.add(phase3);

		return phaseList;
	}

	private Node getNodeByName(String name) {
		for (Node n : nodesList) {
			if (n.getName().equals(name)) {
				return n;
			}
		}
		return null;
	}

	double phi = Math.toRadians(40);
	int barb = 8;

	private void drawArrowHead(Graphics2D g2, Point tip, Point tail, Color color) {
		g2.setPaint(color);
		double dy = tip.y - tail.y;
		double dx = tip.x - tail.x;
		double theta = Math.atan2(dy, dx);
		double x, y, rho = theta + phi;
		for (int j = 0; j < 2; j++) {
			x = tip.x - barb * Math.cos(rho);
			y = tip.y - barb * Math.sin(rho);
			g2.draw(new Line2D.Double(tip.x, tip.y, x, y));
			rho = theta - phi;
		}
	}

	private Point getCenterPoint(Point start, Point end) {
		int x = (end.x + start.x) / 2;
		int y = (end.y + start.y) / 2;
		Point middle = new Point(x, y);

		return middle;
	}

	public int getPanelHeight() {
		return panelHeight;
	}

	public int getPanelWidth() {
		return panelWidth;
	}

	public void setPanelHeight(int panelHeight) {
		this.panelHeight = panelHeight;
	}

	public void setPanelWidth(int panelWidth) {
		this.panelWidth = panelWidth;
	}

}
